/**
 *    Copyright 2009-2018 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.session;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;

import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;

/**
 * Builds {@link SqlSession} instances.
 * 通过配置文件构建SqlSessionFactory的工厂.工厂模式
 * 构建工厂的工厂
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {

  //SqlSessionFactoryBuilder有9个build()方法

  //build重载方法，根据用户传递的参数决定具体调用哪一个方法，最终执行的都是下面第4种方法
  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }



  /**
   *  具体做事情的方式，通过XMLConfigBuilder解析mybatis配置，然后创建SqlSessionFactory对象
   * @param reader  读取全局配置文件以字符流方式
   * @param environment   可选参数，当前环境(可在全局配置文件中配置，开发环境/生产环境/测试环境)
   * @param properties  可选参数，加载properties（属性配置文件），属性可以用${propName}语法形式在配置文件中使用。和Spring很像，一个思想？
   *                    properties具体是做什么的，还不知道.
   * @return
   */
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      //1. 创建XMLConfigBuilder对象用来解析XML配置文件，生成Configuration对象
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      //2. 将XML配置文件内的信息解析成Java对象Configuration对象, 根据Configuration对象创建出SqlSessionFactory对象
      return build(parser.parse());
    } catch (Exception e) {
      //这里通过自定义异常包装Exception
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      //finally执行在throw执行之后，throw抛出之前，因此需要清空ThreadLoal中的对象
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  //类似上面的方式，不过是以字节流的方式构建的
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  /**
   * 具体做事情的方式，通过XMLConfigBuilder解析mybatis配置，然后创建SqlSessionFactory对象 TODO
   * @param inputStream
   * @param environment
   * @param properties
   * @return
   */
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //1.  创建XML解析器 XMLConfigBuilder对象用来解析XML配置文件，生成Configuration对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //2. 将XML配置文件内的信息解析成Java对象Configuration对象, 根据Configuration对象创建出SqlSessionFactory对象
      return build(parser.parse());
    } catch (Exception e) {
      //这里通过自定义异常包装Exception
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      //获取线程上下文错误，并最终清空
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  /**
   * 从此处可以看出，MyBatis内部通过Configuration对象来创建SqlSessionFactory,用户也可以自己通过API构造好Configuration对象，调用此方法创建SqlSessionFactory
   * @param config  全局配置文件构建Configuration后的对象
   * @return   返回默认的SqlSessionFactory
   */
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}
